package org.cllib.math;

/**
 * Float pointer number implementation based on int
 * 
 * @author <a href="mailto:chenliangcareer@gmail.com">liang chen</a>
 * 
 */
public class Float {
	private final static int MAX_NUM_DECIMAL_PART_BITS = 12;
	private final static int MIN_NUM_DECIMAL_PART_BITS = 0;
	private static int numDecimalPartBits;
	private static int numDecimalPartDigits;
	private static int decimalMask;

	/** marker for the position of the imaginary decimal point **/
	private static int one = 1 << numDecimalPartBits;

	/** mask for the decimal part of the number **/
	private static int mask = one - 1;

	private final static char SIGN_NEGATIVE = '-';
	private final static char SIGN_POSITIVE = '+';
	private final static char DECIMAL_POINT = '.';

	private static boolean debug = false;
	
	static {
		System.out.println("precision [" + setPrecision(MAX_NUM_DECIMAL_PART_BITS) + "]");
	}

	public strictfp static void main(String[] args) {
		String is = "12.12";
		String js = "3.5624";
		
		double di = Double.parseDouble(is);
		double dj = Double.parseDouble(js);
		
		System.out.println();
		
		System.out.println("di = " + di + " dj = " + dj + " i * j = " + (di * dj));
		
		//mul
		System.out.println("MathFP i = " + MathFP.toString(MathFP.toFP(is)) + " j = " + MathFP.toString(MathFP.toFP(js)) + " i * j = " + MathFP.toString(MathFP.mul(MathFP.toFP(is), MathFP.toFP(js))));
		System.out.println("Float i = " + Float.toString(Float.toFloat(is)) + " j = " + Float.toString(Float.toFloat(js)) + " i * j = " + Float.toString(Float.mul(Float.toFloat(is), Float.toFloat(js))));
		
		System.out.println();
		
		System.out.println("di = " + di + " dj = " + dj + " i / j = " + (di / dj));
		
		//div
		System.out.println("MathFP i = " + MathFP.toString(MathFP.toFP(is)) + " j = " + MathFP.toString(MathFP.toFP(js)) + " i / j = " + MathFP.toString(MathFP.div(MathFP.toFP(is), MathFP.toFP(js))));
		System.out.println("Float i = " + Float.toString(Float.toFloat(is)) + " j = " + Float.toString(Float.toFloat(js)) + " i / j = " + Float.toString(Float.div(Float.toFloat(is), Float.toFloat(js))));
		
		System.out.println();
		
		
	}

	private static void debug(String s){
		if(debug){
			System.out.println();
			System.out.println("**************************************** Float debug start ****************************************");
			System.out.println(s);
			System.out.println("**************************************** Float debug end ****************************************");
			System.out.println();
		}
	}
	public static int mul(final int m , final int n){
		int mip = m >> numDecimalPartBits;
		int mdp = m & mask;
		
		int nip = n >> numDecimalPartBits;
		int ndp = n & mask;
		
		debug("mul m = " + toString(m) + "[" + mip + "," + ((mdp * decimalMask) >> numDecimalPartBits) + "] n = " + toString(n) + "[" + nip + "," + ((ndp * decimalMask) >> numDecimalPartBits) + "]");
		
		int p1 = (mip * nip) << numDecimalPartBits;
		int p2 = ((mip << numDecimalPartBits) * ndp) >> numDecimalPartBits;
		int p3 = (mdp * ndp) >> numDecimalPartBits;
		int p4 = ((nip << numDecimalPartBits) * mdp) >> numDecimalPartBits;
		
		return p1 + p2 + p3 + p4;
	}
	
	public static int div(final int m , final int n){
		return (m << numDecimalPartBits) / n;
	}
	
	/**
	 * set precision for the whole Float number
	 * 
	 * Note: only numbers of the same precision can be used to be computed
	 * together
	 * 
	 * @param precision
	 *            new precision
	 * 
	 * 
	 * @return actual precision
	 */
	public static int setPrecision(int precision) {
		if (precision >= MIN_NUM_DECIMAL_PART_BITS
				&& precision <= MAX_NUM_DECIMAL_PART_BITS) {
			numDecimalPartBits = precision;
			one = 1 << numDecimalPartBits;
			mask = one - 1;
			numDecimalPartDigits = getNumberOfDecimalDigitsForBits(numDecimalPartBits);
			decimalMask = getDecimalMask(numDecimalPartDigits);
		}
		return numDecimalPartBits;
	}

	/**
	 * get decimal mask for number of decimal digits
	 * 
	 * for example : numDigits = 2 return 100
	 * 
	 * @param numDigits
	 *            input
	 * 
	 * @return decimal mask
	 */
	public static int getDecimalMask(int numDigits) {
		if (numDigits <= 0) {
			throw new IllegalArgumentException(
					"unable to get decimal mask for number of digits ["
							+ numDigits + "]");
		}
		int value = 1;
		while (numDigits-- > 0) {
			value *= 10;
		}
		return value;
	}

	/**
	 * convert a common integer into a Float format integer
	 * 
	 * @param i
	 *            common integer to convert
	 * 
	 * @return Float formatted integer
	 */
	public static int toFloat(int i) {
		return i << numDecimalPartBits;
	}

	/**
	 * convert a string into a Float format decimal number
	 * 
	 * for example: 45 , 24.244 , -13
	 * 
	 * 
	 * @param s
	 *            the string to convert
	 * 
	 * @return Float formated integer
	 */
	public static int toFloat(String s) {
		int value = 0;
		boolean positive = true;
		if (s.charAt(0) == SIGN_POSITIVE || s.charAt(0) == SIGN_NEGATIVE) {
			if (s.charAt(0) == SIGN_NEGATIVE) {
				positive = false;
			}
			s = s.substring(1);
		}

		String decimalString = null;
		String integerString = null;

		int decimalPointIndex = s.indexOf(DECIMAL_POINT);
		if (decimalPointIndex >= 0) {
			// both integer and decimal part
			decimalString = s.substring(decimalPointIndex + 1);
			while (decimalString.length() < numDecimalPartDigits) {
				decimalString += "0";
			}

			if (decimalString.length() > numDecimalPartDigits) {
				decimalString = decimalString
						.substring(0, numDecimalPartDigits);
			}

			integerString = s.substring(0, decimalPointIndex);
		} else {
			// just integer part
			integerString = new String(s);
			decimalString = "0";
		}

		int integer = Integer.parseInt(integerString);
		int decimal = Integer.parseInt(decimalString);

		// TODO in MathFP decimal += 1; i do not see why ,maybe i should check
		// this

		value = (integer << numDecimalPartBits)
				+ (decimal << numDecimalPartBits) / decimalMask;

		if (!positive) {
			value = -value;
		}
		return value;
	}

	/**
	 * test whether a Float format number is positive or not
	 * 
	 * @param f
	 *            the number to be tested
	 * 
	 * @return whether the number is positive or not
	 */
	public static boolean isPositive(int f) {
		return (f & 0x80000000) == 0;
	}

	/**
	 * compute the sum of tow Float format number
	 * 
	 * @param f1
	 *            first operand
	 * @param f2
	 *            second operand
	 * 
	 * @return sum of the two operands
	 */
	public static int add(int f1, int f2) {
		// TODO how to deal with overflow or as a user you should know what you
		// are doing ?
		return f1 + f2;
	}

	/**
	 * compute the result of f1 substract f2
	 * 
	 * @param f1
	 *            the minuend
	 * @param f2
	 *            the subtrahend
	 * 
	 * @return the difference
	 */
	public static int sub(int f1, int f2) {
		return f1 - f2;
	}

	/**
	 * get the number of decimal digits for the numBits bits
	 * 
	 * @param numBits
	 *            number of bits
	 * 
	 * @return number of decimal digits
	 */
	public static int getNumberOfDecimalDigitsForBits(final int numBits) {
		if (numBits < 0) {
			throw new IllegalArgumentException(
					"can not get number of decimal digits for bits < 0 ["
							+ numBits + "]");
		}

		int value = (1 << numBits) - 1;
		int numDecimalDigits = 0;

		do {
			numDecimalDigits++;
			value /= 10;
		} while (value > 0);

		return numDecimalDigits;
	}
	
	public static String toString(final int n){
		String is = String.valueOf(n >> numDecimalPartBits);
		String ds = String.valueOf(((n & mask) * decimalMask) >> numDecimalPartBits);
		
		while(ds.length() < numDecimalPartDigits){
			ds += "0";
		}
		
		ds = ds.substring(0 , numDecimalPartDigits);
		
		return is + "." +  ds;
	}
}
